package edu.upenn.seas.peerreview.pr

import edu.upenn.seas.peerreview.{Id, Source, Sink}
import edu.upenn.seas.peerreview.pr.PRMessages._
import java.io.Serializable
import scala.collection.mutable.Queue

/* ********************************** */
// Handles logging and auditing.

class Log extends Serializable {
  val startTime: Long = System.currentTimeMillis
  type LogEntry = (SeqNum, LogMessage, Hash)
  val log : Queue[LogEntry] = new Queue()
  val debug=edu.upenn.seas.peerreview.Debug.debug("log")(_)
 //change this to "==" if we want audit statements to show
  var lastHash = 0

  // msgtype = 1 if LOG_RECV, 2 if LOG_SEND
  def calcHash(pastHash: Int, seqNum: Int, msgType: Int, entryHash: Int) : Int =  {
    var result : Int = 7
    result = pastHash*result*41
    result = seqNum*result*41
    result = msgType*result*41
    result = entryHash*result*41
    result
  }
  def addRecv(from: Id, entry : Message, idSent: SeqNum) : SeqNum = {
    val Seq : Int = (System.currentTimeMillis - startTime).toInt
    val newHash = calcHash(lastHash, Seq, 1, entry.hashCode)
    lastHash = newHash
    log += (Seq, LOG_RECV(from, entry, idSent), newHash)
    Seq
  }

  def addSent(to: Id, entry: Message) : SeqNum= {
    val Seq = (System.currentTimeMillis - startTime).toInt
    val newHash = calcHash(lastHash, Seq, 2, entry.hashCode)
    lastHash = newHash
    log += (Seq, LOG_SEND(to, entry), newHash)
    Seq
  }
  
  /**
   * Takes a function which is used to instantiate a new state machine,
   * and audits to ensure that the hash is properly calculated, and that
   * the messages correspond to what a state machine should do.
   */
  def audit(f: (Sink.T) => Source.T) : Boolean = {
    /* Auditing: Episode I: The Hash Chain
     * This ensures that the hash chain is maintained.
     */
    
    val newHashLog = log.clone()
    var lastAuditHash = 0
    while(!(newHashLog.isEmpty)) {
      val entry = newHashLog.dequeue
      val msg = entry._2
      // this is an ugly part that will need to be refined, 
      // especially as we add new messages in
      var msgType = if(msg.isInstanceOf[LOG_SEND]) LOG_SEND else if (msg.isInstanceOf[LOG_RECV]) LOG_RECV else LOG_ACK
      val msgContent = msgType match {
        case LOG_SEND => msg.asInstanceOf[LOG_SEND].msg
        case LOG_RECV => msg.asInstanceOf[LOG_RECV].msg
        case _ => ""
      }
  
      if(entry._3 != calcHash(lastAuditHash, entry._1, if(msgType==LOG_RECV) 1 else 2, msgContent.hashCode)) {
	debug("Hash chain failed.")
        return false 
      }
      else 
        lastAuditHash = 
          calcHash(lastAuditHash, entry._1, if(msgType==LOG_RECV) 1 else 2, msgContent.hashCode)
    }

    /*
     * Auditing: Episode II: The Simulation
     *
     * This is the part that instantiates a new SM, and makes sure messages match
     * 
     * Ways an audit can fail:
     * 1) expected to not send another message, but it did.
     * (send instead of recv)
     * 2) expected to send a message, but it didn't. (recv instead of send)
     * 3) sent the wrong message
     */
    
    val newLog = log.clone()
    val s = new Sink.T {
      var correct: Boolean = true
      def apply(to: Id, m: Message) {
        // check if that message was the next one..
        if(m.isInstanceOf[PRUIMessage]) return
        if(newLog.isEmpty) return
        val nextEntry: LogEntry = newLog.dequeue
        val nextMsg: LogMessage = nextEntry._2
        nextMsg match {
          // if it's the same as (to, m), we're done
          // if not, audit should return false
          case LOG_SEND(a, b) if a==to && b==m => {
            // do nothing, keep rolling
            debug("GOOD SEND: To: "+a+" MSG: "+b.toString)
          }
          case LOG_SEND(a, b) => {
            debug("Wrong recipient/msg")
            debug("TO: " + to + " MSG: " + m.toString)
            debug("EXPECTED TO: " + a + " MSG: " + b.toString)
            correct = false
          }
          case LOG_RECV(a, b, c) =>{
            debug("Sent new message when receive expected.")
            debug("TO: " + to + " MSG: " + m.toString)
            debug("EXPECTED FROM: " + a + " MSG: " + b.toString)
            correct = false// "return" false
          }
        } //end match
      }
    } //end sink
    
    // should be SM, but "sends" messages through s instead of Transport 
    val h = f(s) 
    //start dequeuing messages
    //if recv message, send it through the handler to SM
    // if sent message, something's wrong => return false
    // if queue empties, then everything checks out => return true
    while(!(newLog isEmpty)) {
      if(s.correct == false)
        return false
      if(log isEmpty) return true
      val entry : LogEntry = newLog.dequeue
      val msg : LogMessage = entry._2
      msg match {
        case LOG_SEND(a, b) => {
          debug("SM should have sent a message by now.")
          debug("EXPECTED TO: " + a + "MSG: " + b.toString)
          return false
        }
        case LOG_RECV(from, msg, id) =>{
          debug("GOOD RECEIVE: from: "+from+" msg: " + msg.toString)
          h(from, msg)
        }
        case _ => error("something in the log that doesn't belong")
      }
    } //end loop
    true  //our return
  } //end audit function
}
